﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using DotNetCommon.Extensions;

namespace DBUtil.Builders
{
    public class SaveOneBuilder<T> : BaseBuilder where T : class, new()
    {
        private T ent;
        private EntityInfo EntityInfo { get; set; }
        public string TableName { get; private set; }

        internal SaveOneBuilder(DBAccess db, T ent) : base(db)
        {
            this.ent = ent ?? throw new Exception($"要保存的数据不能为空!");
            EntityInfo = db.GetEntityInfoInternal<T>();
            if (EntityInfo.PrimaryKeyColumn == null) throw new Exception($"class {EntityInfo.TypeClassFullName} 没有配置主键, 不能用于 SaveOneBuilder!");
            this.TableName = EntityInfo.TableNameSeg;
        }
        internal SaveOneBuilder(DBAccess db) : base(db)
        {
            EntityInfo = db.GetEntityInfoInternal<T>();
            if (EntityInfo.PrimaryKeyColumn == null) throw new Exception($"class {EntityInfo.TypeClassFullName} 没有配置主键, 不能用于 SaveOneBuilder!");
            this.TableName = EntityInfo.TableNameSeg;
        }

        public SaveOneBuilder<T> SetEntity(T entity)
        {
            this.ent = entity ?? throw new Exception($"要保存的数据不能为空!");
            return this;
        }

        #region AsTable
        public virtual SaveOneBuilder<T> AsTable(Func<string, string> func)
        {
            var newTableName = func?.Invoke(TableName);
            if (newTableName.IsNotNullOrEmptyOrWhiteSpace()) TableName = newTableName;
            return this;
        }

        public virtual SaveOneBuilder<T> AsTableIf(bool condition, Func<string, string> func)
            => condition ? AsTable(func) : this;
        #endregion

        internal readonly List<SetIgnoreItem> insertSetIgnores = [];
        internal readonly List<SetIgnoreItem> updateSetIgnores = [];
        #region Both SetColumn & IgnoreColumns
        //SetColumn
        public SaveOneBuilder<T> SetColumn(string colname, object value)
        {
            SetIgnoreItem.SetColumn(db, insertSetIgnores, colname, value);
            SetIgnoreItem.SetColumn(db, updateSetIgnores, colname, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIf(bool condition, string colname, object value) => condition ? SetColumn(colname, value) : this;
        public SaveOneBuilder<T> IgnoreColumns(params string[] colNames)
        {
            SetIgnoreItem.IgnoreColumns(db, insertSetIgnores, colNames);
            SetIgnoreItem.IgnoreColumns(db, updateSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIf(bool condition, params string[] colNames) => condition ? IgnoreColumns(colNames) : this;
        public SaveOneBuilder<T> OnlyColumns(params string[] colNames)
        {
            SetIgnoreItem.OnlyColumns(db, insertSetIgnores, colNames);
            SetIgnoreItem.OnlyColumns(db, updateSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIf(bool condition, params string[] colNames) => condition ? OnlyColumns(colNames) : this;


        //expr
        public SaveOneBuilder<T> SetColumn(Expression<Func<T, object>> propSelector, object value)
        {
            SetIgnoreItem.SetColumn(db, insertSetIgnores, EntityInfo, propSelector, value);
            SetIgnoreItem.SetColumn(db, updateSetIgnores, EntityInfo, propSelector, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIf(bool condition, Expression<Func<T, object>> propSelector, object value)
            => condition ? SetColumn(propSelector, value) : this;
        public SaveOneBuilder<T> SetColumn(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
        public SaveOneBuilder<T> SetColumnIf(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
           => condition ? SetColumn(propSelector, setExp) : this;
        public SaveOneBuilder<T> IgnoreColumns(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.IgnoreColumns(db, insertSetIgnores, EntityInfo, propSelector);
            SetIgnoreItem.IgnoreColumns(db, updateSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIf(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? IgnoreColumns(propSelector) : this;
        public SaveOneBuilder<T> OnlyColumns(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.OnlyColumns(db, insertSetIgnores, EntityInfo, propSelector);
            SetIgnoreItem.OnlyColumns(db, updateSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIf(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? OnlyColumns(propSelector) : this;
        #endregion

        #region Insert SetColumn & IgnoreColumns
        //SetColumn
        public SaveOneBuilder<T> SetColumnForInsert(string colname, object value)
        {
            SetIgnoreItem.SetColumn(db, insertSetIgnores, colname, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, string colname, object value) => condition ? SetColumnForInsert(colname, value) : this;
        public SaveOneBuilder<T> IgnoreColumnsForInsert(params string[] colNames)
        {
            SetIgnoreItem.IgnoreColumns(db, insertSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIfForInsert(bool condition, params string[] colNames) => condition ? IgnoreColumnsForInsert(colNames) : this;
        public SaveOneBuilder<T> OnlyColumnsForInsert(params string[] colNames)
        {
            SetIgnoreItem.OnlyColumns(db, insertSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIfForInsert(bool condition, params string[] colNames) => condition ? OnlyColumnsForInsert(colNames) : this;


        //expr
        public SaveOneBuilder<T> SetColumnForInsert(Expression<Func<T, object>> propSelector, object value)
        {
            SetIgnoreItem.SetColumn(db, insertSetIgnores, EntityInfo, propSelector, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, Expression<Func<T, object>> propSelector, object value)
            => condition ? SetColumnForInsert(propSelector, value) : this;
        public SaveOneBuilder<T> SetColumnForInsert(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
        public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
           => condition ? SetColumnForInsert(propSelector, setExp) : this;
        public SaveOneBuilder<T> IgnoreColumnsForInsert(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.IgnoreColumns(db, insertSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIfForInsert(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? IgnoreColumnsForInsert(propSelector) : this;
        public SaveOneBuilder<T> OnlyColumnsForInsert(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.OnlyColumns(db, insertSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIfForInsert(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? OnlyColumnsForInsert(propSelector) : this;
        #endregion

        #region Update SetColumn & IgnoreColumns
        //SetColumn
        public SaveOneBuilder<T> SetColumnForUpdate(string colname, object value)
        {
            SetIgnoreItem.SetColumn(db, updateSetIgnores, colname, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, string colname, object value) => condition ? SetColumnForUpdate(colname, value) : this;
        public SaveOneBuilder<T> IgnoreColumnsForUpdate(params string[] colNames)
        {
            SetIgnoreItem.IgnoreColumns(db, updateSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIfForUpdate(bool condition, params string[] colNames) => condition ? IgnoreColumnsForUpdate(colNames) : this;
        public SaveOneBuilder<T> OnlyColumnsForUpdate(params string[] colNames)
        {
            SetIgnoreItem.OnlyColumns(db, updateSetIgnores, colNames);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIfForUpdate(bool condition, params string[] colNames) => condition ? OnlyColumnsForUpdate(colNames) : this;


        //expr
        public SaveOneBuilder<T> SetColumnForUpdate(Expression<Func<T, object>> propSelector, object value)
        {
            SetIgnoreItem.SetColumn(db, updateSetIgnores, EntityInfo, propSelector, value);
            return this;
        }
        public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, Expression<Func<T, object>> propSelector, object value)
            => condition ? SetColumnForUpdate(propSelector, value) : this;
        public SaveOneBuilder<T> SetColumnForUpdate(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
        public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
           => condition ? SetColumnForUpdate(propSelector, setExp) : this;
        public SaveOneBuilder<T> IgnoreColumnsForUpdate(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.IgnoreColumns(db, updateSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> IgnoreColumnsIfForUpdate(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? IgnoreColumnsForUpdate(propSelector) : this;
        public SaveOneBuilder<T> OnlyColumnsForUpdate(Expression<Func<T, object>> propSelector)
        {
            SetIgnoreItem.OnlyColumns(db, updateSetIgnores, EntityInfo, propSelector);
            return this;
        }
        public SaveOneBuilder<T> OnlyColumnsIfForUpdate(bool condition, Expression<Func<T, object>> propSelector)
            => condition ? OnlyColumnsForUpdate(propSelector) : this;
        #endregion

        public string ToSql()
        {
            var sqlInsert = db.Insert<T>().AsTable(i => TableName).InsertIdentity().SetEntity(ent).ReplaceSetIgnores(insertSetIgnores).ToSql().TrimEnd(';');
            var sqlUpdate = db.Update<T>().SetEntity(ent).ReplaceSetIgnores(updateSetIgnores).ToSqlOnlySetSeg();
            return $"{sqlInsert} on duplicate key update {sqlUpdate};";
        }

        public int ExecuteAffrows()
        {
            var sql = ToSql();
            return RunWriteMonitor(new AfterWriteArgument
            {
                TableName = TableName,
                WriteType = EnumWriteType.SaveOne
            }, () => db.ExecuteSql(sql, CommandType.Text, TimeoutSeconds));
        }

        public async Task<int> ExecuteAffrowsAsync(CancellationToken cancellationToken = default)
        {
            var sql = ToSql();
            return await RunWriteMonitorAsync(new AfterWriteArgument
            {
                TableName = TableName,
                WriteType = EnumWriteType.SaveOne
            }, async () => await db.ExecuteSqlAsync(sql, CommandType.Text, TimeoutSeconds, null, cancellationToken));
        }
    }
}
